// Copyright (c) 2007 Alistair Jones
// This program is made available under the terms of the MIT License.
package org.hypirinha.html.output;

import org.hypirinha.html.Content;
import org.hypirinha.html.attributes.AnyAttribute;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Stack;

public class PrintAdapter {
    
    public String asString(Content content) {
        return asString(content, HtmlDoctype.none);
    }

    public String asString(Content content, Doctype doctype) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        print(content, new PrintStream(outputStream), doctype);
        return outputStream.toString();
    }

    public void print(Content content, PrintStream printStream) {
        print(content, printStream, HtmlDoctype.none);
    }

    public void print(Content content, PrintStream printStream, Doctype doctype) {
        content.visit(new PrintVisitor(printStream, doctype));
    }

    private static class PrintVisitor implements Visitor {

        private final PrintStream printStream;
        private final Doctype doctype;

        private String lineStart = "\n";
        private Stack<ElementContext> stack = new Stack<ElementContext>();

        public PrintVisitor(PrintStream printStream, Doctype doctype) {
            this.printStream = printStream;
            this.doctype = doctype;
            stack.push(new DoctypeContext());
        }

        public void startTag(String tagName, AnyAttribute... attributes) {
            stack.peek().beforeChild();
            stack.push(new ElementContext(tagName)).startTag(attributes);
            increaseIndent();
        }

        public void text(String text) {
            stack.peek().text(text);
        }

        public void endTag() {
            decreaseIndent();
            stack.pop().endTag();
        }

        private void increaseIndent() {
            lineStart += "  ";
        }

        private void decreaseIndent() {
            lineStart = lineStart.substring(0, lineStart.length() - 2);
        }

        private class ElementContext {
            private String tagName;
            private boolean hasChildren = false;
            private boolean firstElementWasText = false;

            ElementContext(String tagName) {
                this.tagName = tagName;
            }

            void startNewLineIfAppropriate() {
                if (hasChildren && !firstElementWasText) {
                    printStream.print(lineStart);
                }
            }

            void startTag(AnyAttribute[] attributes) {
                printStream.print("<" + tagName);
                for (AnyAttribute attribute : attributes) {
                    printStream.print(" " + attribute.getName() + "=\"" + attribute.getValue() + "\"");
                }
                printStream.print(">");
            }

            void text(String text) {
                printStream.print(text);
                if (!hasChildren) firstElementWasText = true;
            }

            void endTag() {
                startNewLineIfAppropriate();
                printStream.print("</" + tagName + ">");
            }

            void beforeChild() {
                hasChildren = true;
                startNewLineIfAppropriate();
            }
        }

        private class DoctypeContext extends ElementContext {
            DoctypeContext() {
                super("");
            }

            public void startNewLineIfAppropriate() {
                printStream.print(doctype.getDtdDeclaration());
            }
        }
    }
}
